﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Concurrent;
using System.Threading;
using System.Runtime.Remoting;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Diagnostics;
using Roslyn.Compilers.CSharp;

using King.Text;
using King.Reflection;
using King.Extensions;
using King.CSharp.Parser;
using System.Text.RegularExpressions;

namespace King.CSharp.Text {

    public sealed class CsTypesAttribute : Attribute {

        public CsTypesAttribute(params Type[] types) { }
    }

    [TextNodeSubstituteTargets(typeof(CSharpKeyword))]
    public sealed class CSharpSubstitute : TextNodes {

        protected override IEnumerable<object> Substitute(TextNodeContext context, object value) {
            yield return value.ToString().ToLower();
        }        
    }

    public sealed class CSharpCompilationUnit : TextNodes {

        private object m_attributes;
        private object m_usings;

        public CSharpCompilationUnit(
            object attributes = null,
            object usings = null) {

            m_attributes = attributes;
            m_usings = usings;
        }
        
        protected override IEnumerable<object> Content() {
            yield return m_attributes;
            yield return m_usings;
            yield return Children();
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextDoubleSpaced() {
                new CSharpUsings(m_usings),

                new TextSingleSpaced() { 
                    new CSharpAttributeGroups(m_attributes) 
                },

                Children()
            };
        }
    }

    internal sealed class CSharpUsings : TextNode {

        private object m_usings;

        internal CSharpUsings(object usings) {
            m_usings = usings;
        }

        protected override IEnumerable<object> Content() {
            yield return m_usings;
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextSingleSpaced() { 
                from o in context.Flatten(m_usings)
                select o is CSharpUsing ? o : new CSharpUsing(o)
            };
        }
    }

    internal sealed class CSharpAttributeGroups : TextNode {

        private object m_groups;

        internal CSharpAttributeGroups(object groups) {
            m_groups = groups;
        }

        protected override IEnumerable<object> Content() {
            yield return m_groups;
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            var group = default(List<object>);

            // group nodes into attribute groups
            foreach (var o in context.Flatten(m_groups)) {

                // node is not already a group so add it to the group under construction
                if (!(o is CSharpAttributeGroup)) {

                    if (group == null)
                        group = new List<object>();

                    group.Add(o);

                // node is already a group so yield any group under construction followed by this group
                } else {

                    if (group != null)
                        yield return new CSharpAttributeGroup() { group };

                    group = null;

                    yield return o;
                }
            }

            // no more groups so yield any new group under construction
            if (group != null)
                yield return new CSharpAttributeGroup() { group };
        }
    }
    
    public sealed class CSharpAttributeGroup : TextNodes {

        private object m_target;

        public CSharpAttributeGroup(
            object target = null) {

            m_target = target;
        }

        protected override IEnumerable<object> Content() {
            return Children();
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            yield return "[";

            if (context.ContainsContent(m_target)) {
                yield return m_target;
                yield return ": ";
            }

            yield return new TextJoin(", ") { 
                from o in context.Flatten(Children())
                select o is CSharpAttribute ? o : new CSharpAttribute(o)
            };

            yield return "]";
        }
    }

    [TextNodeContainsContent]
    public sealed class CSharpUsing : TextNode {

        private object m_name;
        private object m_alias;

        public CSharpUsing(
            object name,
            object alias = null) {

            m_name = name;
            m_alias = alias;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextNodes() { "using", " ", m_name, ";" };
        }
    }

    [TextNodeContainsContent]
    public sealed class CSharpAttribute : TextNode {

        private object m_name;
        private object m_arguments;
        private object m_namedArguments;

        public CSharpAttribute(
            object name,
            object arguments = null,
            object namedArguments = null) {

            m_name = name;
            m_arguments = arguments;
            m_namedArguments = namedArguments;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            yield return m_name;

            if (m_arguments == null && m_namedArguments == null)
                yield break;

            yield return new TextJoin(", ", "(", ")") {
                m_arguments == null ? null : new TextJoin(", ") { m_arguments },
                m_namedArguments == null ? null : new TextNameValue(m_namedArguments)
            };
        }
    }

    public sealed class CSharpAttributeArgument : TextNode {

        public CSharpAttributeArgument(
            object name,
            object expression,
            bool namedProperty = false) {
        }
    }

    public enum CSharpKeyword {
        Using,

        Public,
        Private,
        Internal,
        Protected,

        Class,
        Struct,
        Interface,
        Enum,
        Delegate,

        Abstract,
        Static,
        Sealed,
        Partial,
        Extern,
    }

    public sealed class CSharpGenericParameterConstraint : TextNode {

        private object m_name;
        private object m_constraints;
        private bool m_isClassConstraint;
        private bool m_isStructConstraint;
        private bool m_isNewConstraint;

        public CSharpGenericParameterConstraint(
            object name,
            object constraints,
            bool isClassConstraint = false,
            bool isStructConstraint = false,
            bool isNewConstraint = false) {

            m_name = name;
            m_constraints = constraints;
            m_isClassConstraint = isClassConstraint;
            m_isStructConstraint = isStructConstraint;
            m_isNewConstraint = isNewConstraint;
        }

        protected override IEnumerable<object> Content() {
            if (m_isClassConstraint || m_isStructConstraint || m_isNewConstraint)
                yield return TextNode.ArbitraryContent;
            else
                yield return m_constraints;
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            var prefix = new TextJoin() { "where", m_name, ": " };

            return new TextJoin(prefix: prefix) {
                m_isClassConstraint ? "class" : null,
                m_isStructConstraint ? "struct" : null,
                m_isNewConstraint ? "new()" : null,
                m_constraints,
            };
        }
    }

    public sealed class CSharpType : TextNodes {

        private object m_name;
        private object m_baseType;
        private object m_interfaces;
        private object m_constraints;

        private bool m_isPublic;
        private bool m_isPrivate;
        private bool m_isProtected;
        private bool m_isInternal;
        private bool m_isStruct;
        private bool m_isInterface;
        private bool m_isEnum;
        private bool m_isAbstract;
        private bool m_isStatic;
        private bool m_isPartial;
        private bool m_isSealed;
        private bool m_isExtern;

        private object m_attributes;

        public CSharpType(
            object name,
            object baseType = null,
            object interfaces = null,
            object constraints = null,
            bool isPublic = false,
            bool isPrivate = false,
            bool isProtected = false,
            bool isInternal = false,
            bool isStruct = false,
            bool isInterface = false,
            bool isEnum = false,
            bool isAbstract = false,
            bool isStatic = false,
            bool isPartial = false,
            bool isSealed = false,
            bool isExtern = false,
            object attributes = null) {

            m_name = name;
            m_baseType = baseType;
            m_interfaces = interfaces;
            m_constraints = constraints;

            m_isPublic = isPublic;
            m_isPrivate = isPrivate;
            m_isProtected = isProtected;
            m_isInternal = isInternal;

            m_isStruct = isStruct;
            m_isInterface = isInterface;
            m_isEnum = isEnum;
            m_isAbstract = isAbstract;
            m_isStatic = isStatic;
            m_isPartial = isPartial;
            m_isSealed = isSealed;
            m_isExtern = isExtern;

            m_attributes = attributes;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            var modifiers = new List<object>();

            if (m_isPublic) modifiers.Add(CSharpKeyword.Public);
            if (m_isPrivate) modifiers.Add(CSharpKeyword.Private);
            if (m_isInternal) modifiers.Add(CSharpKeyword.Internal);
            if (m_isProtected) modifiers.Add(CSharpKeyword.Protected);

            if (m_isExtern) modifiers.Add(CSharpKeyword.Extern);
            if (m_isAbstract) modifiers.Add(CSharpKeyword.Abstract);
            if (m_isSealed) modifiers.Add(CSharpKeyword.Sealed);
            if (m_isStatic) modifiers.Add(CSharpKeyword.Static);
            if (m_isPartial) modifiers.Add(CSharpKeyword.Partial);

            if (m_isStruct) modifiers.Add(CSharpKeyword.Struct);
            else if (m_isInterface) modifiers.Add(CSharpKeyword.Interface);
            else if (m_isEnum) modifiers.Add(CSharpKeyword.Enum);
            else modifiers.Add(CSharpKeyword.Class);

            yield return new CSharpSubstitute() { 

                new TextSingleSpaced() {
                    new CSharpAttributeGroups(m_attributes),

                    new TextNodes() {
                        new TextJoin(" ") { modifiers, m_name },

                        new TextIndent {
                            new TextSingleSpaced(Environment.NewLine) { 
                                new TextJoin(", ", ": ") { m_baseType, m_interfaces },
                                m_constraints 
                            },
                        },

                        " ", new CSharpBlock(inline: true) { Children() }
                    }
                }
            };
        }
    }

    public sealed class CSharpBlock : TextNodes {

        private bool m_openBraceOnNewLine;
        private bool m_inline;
        private bool m_force;

        public CSharpBlock(
            bool openBraceOnNewLine = false,
            bool inline = true,
            bool force = true) {

            m_openBraceOnNewLine = openBraceOnNewLine;
            m_inline = inline;
            m_force = force;
        }

        protected override IEnumerable<object> Content() {
            if (m_force)
                return TextNode.ArbitraryContent;

            return Children();
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            var containsContent = context.ContainsContent(Children());
            var content = new TextSingleSpaced() { Children() };
            var inline = m_inline && !context.ContainsNewLine(content);

            if (inline) {
                yield return "{" + " ";

                if (containsContent) {
                    yield return Children();
                    yield return " ";
                }

                yield return "}";

            } else {
                if (m_openBraceOnNewLine)
                    yield return Environment.NewLine;

                yield return "{" + Environment.NewLine;

                if (containsContent) {
                    yield return new TextIndent() { 
                        content 
                    };
                    yield return Environment.NewLine;
                }

                yield return "}";
            }
        }
    }

    public abstract class CsNode : TextNodes {

        [TextNodeContainsState]
        private class CsFrame : TextNodes {

            private StringBuilder m_buffer;
            private List<SyntaxNodeOrToken> m_nodeOrTokens;

            internal bool OnCharacter(char character) {
                if (m_buffer == null) {

                    // initialize trivia buffer with trailing trivia of last node
                    if (m_nodeOrTokens != null) {

                        var lastNodeOrToken = m_nodeOrTokens.Last();
                        if (lastNodeOrToken.HasTrailingTrivia)
                            m_buffer = new StringBuilder(lastNodeOrToken.GetTrailingTrivia().ToString());
                    }

                    if (m_buffer == null)
                        m_buffer = new StringBuilder();
                }

                m_buffer.Append(character);
                return true;
            }
            internal void AddNodeOrToken(SyntaxNodeOrToken nodeOrToken) {
                // trivial case when there is no trivia
                if (m_buffer == null) {
                    if (m_nodeOrTokens == null)
                        m_nodeOrTokens = new List<SyntaxNodeOrToken>();
                    m_nodeOrTokens.Add(nodeOrToken);
                    return;
                }

                // if the node already has leading trivia then append it to the buffer
                if (nodeOrToken.HasLeadingTrivia)
                    m_buffer.Append(nodeOrToken.GetLeadingTrivia().ToString());

                // dump trivia so it can be re-parsed and possibly split between adjoining nodes
                var trivia = m_buffer.ToString();
                var leadingTriviaOffset = 0;

                // if this is the first node then create the list
                if (m_nodeOrTokens == null) {
                    m_nodeOrTokens = new List<SyntaxNodeOrToken>();

                    // else the node has and older sibling so split the trivia between them
                } else {
                    var trailingTriva = Syntax.ParseTrailingTrivia(trivia);
                    leadingTriviaOffset = trailingTriva.Count;

                    // replace older siblings trailing trivia with new trailing trivia
                    var olderSibling = m_nodeOrTokens.Last();
                    m_nodeOrTokens[m_nodeOrTokens.Count - 1] = olderSibling.WithTrailingTrivia(trailingTriva);
                }

                // add new node with leading trivia
                m_nodeOrTokens.Add(
                    nodeOrToken.WithLeadingTrivia(
                        Syntax.ParseLeadingTrivia(trivia, leadingTriviaOffset)));
            }
            internal List<SyntaxNodeOrToken> NodesAndtokens {
                get { return m_nodeOrTokens; }
            }
            internal string Text {
                get {
                    if (m_buffer == null)
                        return null;
                    return m_buffer.ToString();
                }
            }

            protected override void Prolog(TextNodeContext context) {
                m_nodeOrTokens = null;
                m_buffer = null;
            }
            protected override void Epilog(TextNodeContext context) {
                if (m_buffer != null && m_nodeOrTokens == null)
                    return;

                // replace last node trailing trivia with all remaining trivia
                if (m_buffer != null && m_nodeOrTokens != null)
                    m_nodeOrTokens[m_nodeOrTokens.Count - 1] =
                        m_nodeOrTokens.Last().WithLeadingTrivia(Syntax.ParseLeadingTrivia(m_buffer.ToString()));

                m_buffer = null;
            }
        }

        private SyntaxNodeOrToken m_nodeOrToken;

        internal CsNode() { }

        internal SyntaxNodeOrToken NodeOrToken {
            get { return m_nodeOrToken; }
        }
        internal SyntaxNode Node {
            get { return m_nodeOrToken.AsNode(); }
        }
        internal SyntaxToken Token {
            get { return m_nodeOrToken.AsToken(); }
        }

        internal virtual IEnumerable<object> Nodes(TextNodeContext context) {
            throw new NotImplementedException();
        }
        internal virtual SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {
            throw new NotImplementedException();
        }
        internal virtual SyntaxNodeOrToken Activate(string code) {
            throw new NotImplementedException();
        }
        internal virtual SyntaxNodeOrToken Activate() {
            return default(SyntaxNodeOrToken);
        }

        protected sealed override IEnumerable<object> Children(TextNodeContext context) {
            // punctuation nodes simply return a token
            var nodeOrToken = Activate();

            // non-trivial nodes require evaluation
            if (nodeOrToken.Kind == SyntaxKind.None) {

                // construct the frame for this CsNode. Child CsNodes push their node or token into this frame
                var frame = new CsFrame() { Nodes(context) };

                // traverse the tree
                context.Evaluate(frame, frame.OnCharacter);

                // activate the node or token for this CsNode
                var nodesAndTokens = frame.NodesAndtokens;
                var text = frame.Text;

                // nodes can be created by construction (internal nodes) or by parsing (leaf nodes)
                if (nodesAndTokens != null)
                    // activate by construction
                    nodeOrToken = Activate(new SyntaxNodesOrTokens(nodesAndTokens));
                else if (text != null)
                    // activate by parsing
                    nodeOrToken = Activate(text);
                else
                    // there was no content captured by the frame
                    yield break;
            }

            // push the node or token to the parent frame (if any)
            var parent = context.Ancestors<CsFrame>().FirstOrDefault();
            if (parent != null)
                parent.AddNodeOrToken(nodeOrToken);

            // save this node or token
            m_nodeOrToken = nodeOrToken;

            // yield the node or token (a parent must substitute it for its text)
            yield return Node;
        }
    }

    public static class CsKeyword {

        static CsKeyword() {
            CsToken.LoadTokens(typeof(CsKeyword), "Keyword");
        }

        public static CsToken Class;
        public static CsToken Struct;
        public static CsToken Interface;
        public static CsToken Enum;
        public static CsToken Out;
        public static CsToken Ref;
    }
    public static class CsPunctuation {

        static CsPunctuation() {
            CsToken.LoadTokens(typeof(CsPunctuation), "Token");
        }

        public static CsToken OpenParen;
        public static CsToken CloseParen;

        public static CsToken OpenBracket;
        public static CsToken CloseBracket;

        public static CsToken OpenBrace;
        public static CsToken CloseBrace;

        public static CsToken Colon;
        public static CsToken Comma;
        public static CsToken Equals;    
    }

    internal struct SyntaxNodesOrTokens : IEnumerable<SyntaxNodeOrToken> {

        private List<SyntaxNodeOrToken> m_nodesOrTokens;

        internal SyntaxNodesOrTokens(List<SyntaxNodeOrToken> nodesOrTokens) {
            m_nodesOrTokens = nodesOrTokens;
        }

        private void ThrowException(int index) {
            throw new InvalidOperationException();
        }

        private SyntaxNodeOrToken Get(ref int index, SyntaxKind? kind = null) {
            var result = TryGet(ref index, kind);
            if (result == null)
                ThrowException(index);

            var nodeOrToken = (SyntaxNodeOrToken)result;
            if (kind != null && nodeOrToken.Kind != kind) {
                index--;
                ThrowException(index);
            }

            return nodeOrToken;
        }
        private SyntaxNodeOrToken? TryGet(ref int index, SyntaxKind? kind = null) {
            if (index >= m_nodesOrTokens.Count)
                return null;

            var nodeOrToken = m_nodesOrTokens[index];
            if (kind != null && nodeOrToken.Kind != kind)
                return null;                

            index++;
            return nodeOrToken;
        }

        internal SyntaxToken? TryGetToken(ref int index, SyntaxKind? kind = null) {
            var result = TryGet(ref index, kind);
            if (result == null)
                return null;

            var value = result.Value;
            if (!value.IsToken) {
                index--;
                return null;
            }

            return value.AsToken();
        }
        internal SyntaxToken GetToken(ref int index, SyntaxKind? kind = null) {
            var result = Get(ref index, kind);
            if (!result.IsToken)
                ThrowException(--index);
            return result.AsToken();
        }
        internal SyntaxToken GetKeyword(ref int index) {
            var result = Get(ref index);
            if (!result.Kind.ToString().EndsWith("Keyword"))
                ThrowException(--index);
            return (SyntaxToken)result;
        }
        internal SyntaxToken? TryGetIdentifier(ref int index) {
            return TryGetToken(ref index, SyntaxKind.IdentifierToken);
        }
        internal SyntaxToken GetEof(ref int index) {
            throw new NotImplementedException();
        }

        internal SyntaxNode TryGetNode(ref int index, SyntaxKind? kind = null) {
            var maybeResult = TryGet(ref index, kind);
            if (maybeResult == null)
                return null;

            var result = (SyntaxNodeOrToken)maybeResult;
            if (!result.IsNode) {
                index--;
                return null;
            }

            return result.AsNode();
        }
        internal SyntaxNode GetNode(ref int index, SyntaxKind? kind = null) {
            var result = Get(ref index, kind);
            if (!result.IsNode)
                ThrowException(--index);
            return result.AsNode();
        }
        internal T TryGet<T>(ref int index) where T : class {
            var result = TryGetNode(ref index);
            if (!(result is T)) {
                index--;
                return null;
            }

            return result as T;
        }
        internal T Get<T>(ref int index) where T : class {
            var result = GetNode(ref index) as T;
            if (result == null)
                ThrowException(index--);
            return result;
        }
        internal ArgumentSyntax GetArgument(ref int index) {
            return Get<ArgumentSyntax>(ref index);
        }
        internal AttributeArgumentSyntax GetAttributeArgument(ref int index) {
            var argument = TryGet<ArgumentSyntax>(ref index);
            if (argument != null) 
                return Syntax.AttributeArgument(
                    nameColonOpt: argument.NameColonOpt, expression: argument.Expression);

            return Get<AttributeArgumentSyntax>(ref index);
        }
        internal ExpressionSyntax GetExpression(ref int index) {
            return Get<ExpressionSyntax>(ref index);
        }
        internal SyntaxToken GetIdentifier(ref int index) {
            return GetToken(ref index, SyntaxKind.IdentifierToken);
        }
        internal NameSyntax GetName(ref int index) {
            return Get<NameSyntax>(ref index);
        }
        
        internal AttributeArgumentListSyntax GetAttributeArguments(ref int index) {
            var maybeOpenParan = TryGetToken(ref index, SyntaxKind.OpenParenToken);
            if (maybeOpenParan == null)
                return null;
           
            var separators = new List<SyntaxToken>();
            var arguments = new List<AttributeArgumentSyntax>();

            var token = GetToken(ref index);
            while (token.Kind != SyntaxKind.CloseParenToken) {
                var argument = GetAttributeArgument(ref index);
                arguments.Add(argument);

                token = GetToken(ref index);
                if (token.Kind == SyntaxKind.CommaToken)
                    separators.Add(token);
            }

            var openParan = maybeOpenParan.Value;
            var closeParan = token;
            
            var separatedList = Syntax.SeparatedList(arguments, separators);
            return Syntax.AttributeArgumentList(openParan, separatedList, closeParan);
        }
        internal AttributeTargetSpecifierSyntax GetAttributeTarget(ref int index) {
            var maybeIdentifier = TryGetIdentifier(ref index);
            if (maybeIdentifier == null)
                return null;

            var identifier = (SyntaxToken)maybeIdentifier;
            var colon = GetToken(ref index, SyntaxKind.ColonToken);
            return Syntax.AttributeTargetSpecifier(identifier, colon);
        }
        internal SeparatedSyntaxList<AttributeSyntax> GetAttributes(ref int index) {
            var separators = new List<SyntaxToken>();
            var arguments = new List<AttributeSyntax>();

            while(true) {
                var argument = Get<AttributeSyntax>(ref index);
                arguments.Add(argument);

                var token = TryGetToken(ref index, SyntaxKind.CommaToken);
                if (token == null)
                    break;

                separators.Add((SyntaxToken)token);
            }

            var separatedList = Syntax.SeparatedList(arguments, separators);
            return separatedList;
        }
        internal SyntaxList<AttributeDeclarationSyntax> GetAttributeGroups(ref int index) {
            throw new NotImplementedException();
        }

        internal object GetMembers(ref int index) {
            throw new NotImplementedException();
        }
        internal SyntaxList<TypeParameterConstraintClauseSyntax> GetConstraints(ref int index) {
            throw new NotImplementedException();
        }
        internal BaseListSyntax GetBaseTypeAndInterfaces(ref int index) {
            throw new NotImplementedException();
        }
        internal TypeParameterListSyntax GetTypeParameters(ref int index) {
            throw new NotImplementedException();
        }
        internal SyntaxTokenList GetModifiers(ref int index) {
            throw new NotImplementedException();
        }

        public IEnumerator<SyntaxNodeOrToken> GetEnumerator() {
            return m_nodesOrTokens.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }

    internal sealed class CsAttributeGroups : TextNode {

        private object m_groups;

        internal CsAttributeGroups(object groups) {
            m_groups = groups;
        }

        protected override IEnumerable<object> Content() {
            yield return m_groups;
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            var group = default(List<object>);

            // group nodes into attribute groups
            foreach (var o in context.Flatten(m_groups)) {

                // node is not already a group so add it to the group under construction
                if (!(o is CSharpAttributeGroup)) {

                    if (group == null)
                        group = new List<object>();

                    group.Add(o);

                    // node is already a group so yield any group under construction followed by this group
                } else {

                    if (group != null)
                        yield return new CSharpAttributeGroup() { group };

                    group = null;

                    yield return o;
                }
            }

            // no more groups so yield any new group under construction
            if (group != null)
                yield return new CSharpAttributeGroup() { group };
        }
    }

    public sealed class CsAttributeGroup : CsNode {

        private object m_target;

        public CsAttributeGroup(
            object target = null) {

            m_target = target;
        }

        protected override IEnumerable<object> Content() {
            return Children();
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            yield return CsPunctuation.OpenBracket;

            if (context.ContainsContent(m_target)) {
                yield return new CsIdentifier(m_target);
                yield return CsPunctuation.Colon;
                yield return " ";
            }

            var commaSpace = new object[] { CsPunctuation.Comma, " " };

            yield return new TextJoin(commaSpace) { 
                context.Flatten(Children())
                    .Select(o => o is CsAttribute ? o : new CsAttribute(o))
            };

            yield return CsPunctuation.CloseBracket;
        }
        internal override SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {
            var index = 0;
            var openBracket = nodesOrTokens.GetToken(ref index, SyntaxKind.OpenBracketToken);
            var target = nodesOrTokens.GetAttributeTarget(ref index);
            var attributes = nodesOrTokens.GetAttributes(ref index);
            var closeBracket = nodesOrTokens.GetToken(ref index, SyntaxKind.CloseBracketToken);

            return Syntax.AttributeDeclaration(openBracket, target, attributes, closeBracket);
        }
    }

    public sealed class CsArgument : CsNode {

        private object m_argument;
        private object m_name;
        private bool m_isNamedMember, m_isRef, m_isOut;

        public CsArgument(object argument) {
            m_argument = argument;
        }
        public CsArgument(object name, object argument, bool isNamedMember = false, bool isRef = false, bool isOut = false) {
            m_name = name;
            m_argument = argument;
            m_isNamedMember = isNamedMember;
            m_isRef = isRef;
            m_isOut = isOut;
        }

        protected override IEnumerable<object> Content() {
            yield return m_argument;
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            if (m_name == null) {
                yield return m_argument;

            } else {
                yield return new CsIdentifier(m_name);

                if (m_isNamedMember)
                    yield return new object[] { " ", CsPunctuation.Equals };
                else
                    yield return CsPunctuation.Colon;

                if (m_isOut)
                    yield return new object[] { " ", CsKeyword.Out  };
                else if (m_isRef)
                    yield return new object[] { " ", CsKeyword.Ref };

                yield return " ";

                yield return new CsExpression(m_argument);
            }
        }
        internal override SyntaxNodeOrToken Activate(string code) {
            return Syntax.ParseArgumentList(code).ChildNodes().First();
        }
        internal override SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {
            var index = 0;

            var nameColon = default(NameColonSyntax);

            var maybeIdentifier = nodesOrTokens.TryGetIdentifier(ref index);
            var expression = nodesOrTokens.GetExpression(ref index);

            if (maybeIdentifier != null) {
                var identifier = (SyntaxToken)maybeIdentifier;

                var separator = nodesOrTokens.GetToken(ref index);
                if (separator.Kind == SyntaxKind.EqualsToken)
                    return Syntax.AttributeArgument(
                        nameEqualsOpt: Syntax.NameEquals(identifier, separator), expression: expression);
                
                nameColon = Syntax.NameColon(identifier, separator);
            }

            var maybeRefOrOutKeyword = nodesOrTokens.TryGetToken(ref index);

            // if this argument is used in a custom attribute it will be transformed into a AttributeArgument
            return Syntax.Argument(nameColon, (SyntaxToken)maybeRefOrOutKeyword, expression);
        }
    }

    public sealed class CsExpression : CsNode {

        private object m_expression;

        public CsExpression(object expression) {
            m_expression = expression;
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            yield return m_expression;
        }
        internal override SyntaxNodeOrToken Activate(string code) {
            return Syntax.ParseExpression(code);
        }
    }

    [TextNodeContainsContent]
    public sealed class CsAttribute : CsNode {

        private object m_name;
        private object m_arguments;
        private object m_namedArguments;

        public CsAttribute(
            object name,
            object arguments = null,
            object namedArguments = null) {

            m_name = name;
            m_arguments = arguments;
            m_namedArguments = namedArguments;
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            yield return new CsName(m_name);

            if (m_arguments == null && m_namedArguments == null)
                yield break;

            var commaSpace = new object[] { CsPunctuation.Comma, " " };

            yield return new TextJoin(commaSpace, CsPunctuation.OpenParen, CsPunctuation.CloseParen) {
                m_arguments == null ? null : new TextJoin(commaSpace) { m_arguments },
                m_namedArguments == null ? null : new TextNameValue(m_namedArguments)
            };
        }
        internal override SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {
            int index = 0;

            return Syntax.Attribute(
                nodesOrTokens.GetName(ref index),
                nodesOrTokens.GetAttributeArguments(ref index)
            );
        }
    }

    [TextNodeContainsContent]
    public sealed class CsToken : CsNode {
                
        internal static void LoadTokens(Type type, string suffix) {
            var fieldsAndKeywords =
                from field in type.GetFields()
                join keyword in typeof(SyntaxKind).GetFields() 
                    on field.Name + suffix equals keyword.Name
                select new {
                    Field = field,
                    Keyword = keyword
                };

            foreach (var o in fieldsAndKeywords)
                o.Field.SetValue(value: new CsToken(Syntax.Token((SyntaxKind)o.Keyword.GetValue())));
        }

        private SyntaxToken m_token;

        internal CsToken(SyntaxToken token) {
            m_token = token;
        }

        internal SyntaxToken SyntaxToken {
            get { return m_token; }
        }

        internal override SyntaxNodeOrToken Activate() {
            return m_token;
        }

        public string FullText {
            get { return m_token.GetFullText(); }
        }
        public string Text {
            get { return m_token.GetText(); }
        }

        public override string ToString() {
            return m_token.GetFullText();
        }
    }

    public sealed class CsModifiers : CsNode {

        public CsModifiers(
            bool isPublic = false,
            bool isPrivate = false,
            bool isProtected = false,
            bool isInternal = false,

            bool isStruct = false,
            bool isInterface = false,
            bool isEnum = false,

            bool isAbstract = false,
            bool isStatic = false,
            bool isPartial = false,
            bool isSealed = false,
            bool isExtern = false) {
            }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            throw new NotImplementedException();
        }
        internal override SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {
            throw new NotImplementedException();
        }
    }

    [TextNodeContainsContent]
    public sealed class CsTypeDefinition : CsNode {

        private class Info {
            internal object Identifier;
            internal object TypeParameters;
            internal object Modifiers;
            internal object Attributes;

            internal bool IsStruct;
            internal bool IsInterface;
            internal bool IsEnum;

            internal object BaseTypesAndInterfaces;
            internal object ConstraintClauses;
        }

        private Info m_info;

        public CsTypeDefinition(
            object identifier,
            object typeParameters = null,
            object modifiers = null,
            object attributes = null,

            bool isStruct = false,
            bool isInterface = false,
            bool isEnum = false,

            object baseTypesAndInterfaces = null,
            object constraintClauses = null) {

            m_info = new Info {
                Identifier = identifier,
                TypeParameters = typeParameters,
                Modifiers = modifiers,
                Attributes = attributes,

                IsStruct = isStruct,
                IsInterface = isInterface,
                IsEnum = isEnum,

                BaseTypesAndInterfaces = baseTypesAndInterfaces,
                ConstraintClauses = constraintClauses
            };
        }

        private ClassDeclarationSyntax ClassDeclarationSyntax {
            get { return Node as ClassDeclarationSyntax; }
        }
        private StructDeclarationSyntax StructDeclarationSyntax {
            get { return Node as StructDeclarationSyntax; }
        }
        private InterfaceDeclarationSyntax InterfaceDeclarationSyntax {
            get { return Node as InterfaceDeclarationSyntax; }
        }
        private EnumDeclarationSyntax EnumDeclarationSyntax {
            get { return Node as EnumDeclarationSyntax; }
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {

            var keyword = CsKeyword.Class;
            if (m_info.IsStruct) 
                keyword = CsKeyword.Struct;
            else if (m_info.IsInterface) 
                keyword = CsKeyword.Interface;
            else if (m_info.IsEnum) 
                keyword = CsKeyword.Enum;

            yield return new TextSingleSpaced() {
                new CsAttributeGroups(m_info.Attributes),

                new TextNodes() {
                    new TextJoin(" ") { m_info.Attributes, keyword, new CsIdentifier(m_info.Identifier) },

                    new TextIndent {
                        new TextSingleSpaced(Environment.NewLine) { 
                            new TextJoin(", ", ": ") { m_info.BaseTypesAndInterfaces },
                            m_info.ConstraintClauses 
                        },
                    },

                    " ", new CsBlock(inline: true) { Children() }
                }
            };
        }
        internal override SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {

            var index = 0;

            //var attributes = nodesOrTokens.GetAttributes(ref index);
            //var modifiers = nodesOrTokens.GetModifiers(ref index);
            var keyword = nodesOrTokens.GetKeyword(ref index);
            var identifier = nodesOrTokens.GetIdentifier(ref index);
            //var typeParameters = nodesOrTokens.GetTypeParameters(ref index);
            //var baseTypeAndInterfaces = nodesOrTokens.GetBaseTypeAndInterfaces(ref index);
            //var constraints = nodesOrTokens.GetConstraints(ref index);
            var token = nodesOrTokens.GetToken(ref index);

            var openBrace = default(SyntaxToken);
            var members = default(object);
            var closeBrace = default(SyntaxToken);
            var semicolon = default(SyntaxToken);

            if (token.Kind == SyntaxKind.OpenBraceToken) {
                openBrace = token;
                members = nodesOrTokens.GetMembers(ref index);
                closeBrace = nodesOrTokens.GetToken(ref index);
            } else {
                semicolon = token;
            }

            var eof = nodesOrTokens.GetEof(ref index);

            //if (keyword.Kind == SyntaxKind.ClassKeyword)
            //    return Syntax.ClassDeclaration(attributes, modifiers, keyword, identifier, typeParameters,
            //        baseTypeAndInterfaces, constraints, openBrace, (SyntaxList<MemberDeclarationSyntax>)members, closeBrace, semicolon);

            //if (keyword.Kind == SyntaxKind.InterfaceKeyword)
            //    return Syntax.InterfaceDeclaration(attributes, modifiers, keyword, identifier, typeParameters,
            //        baseTypeAndInterfaces, constraints, openBrace, (SyntaxList<MemberDeclarationSyntax>)members, closeBrace, semicolon);

            //if (keyword.Kind == SyntaxKind.StructKeyword)
            //    return Syntax.StructDeclaration(attributes, modifiers, keyword, identifier, typeParameters,
            //        baseTypeAndInterfaces, constraints, openBrace, (SyntaxList<MemberDeclarationSyntax>)members, closeBrace, semicolon);

            //if (keyword.Kind == SyntaxKind.EnumKeyword)
            //    return Syntax.EnumDeclaration(attributes, modifiers, keyword, identifier,
            //        baseTypeAndInterfaces, openBrace, (SeparatedSyntaxList<EnumMemberDeclarationSyntax>)members, closeBrace, semicolon);

            throw new NotSupportedException();
        }
    }

    public sealed class CsCompilationUnit : CsNode {

        private object m_attributes;
        private object m_usings;

        public CsCompilationUnit(
            object attributes = null,
            object usings = null) {

            m_attributes = attributes;
            m_usings = usings;
        }

        protected override IEnumerable<object> Content() {
            yield return m_attributes;
            yield return m_usings;
            yield return Children();
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            return new TextDoubleSpaced() {
                new CSharpUsings(m_usings),

                new TextSingleSpaced() { 
                    new CSharpAttributeGroups(m_attributes) 
                },

                Children()
            };
        }
        internal override SyntaxNodeOrToken Activate(SyntaxNodesOrTokens nodesOrTokens) {
            throw new NotImplementedException();
        }
    }

    public sealed class CsBlock : TextNodes {

        private bool m_openBraceOnNewLine;
        private bool m_inline;
        private bool m_force;

        public CsBlock(
            bool openBraceOnNewLine = false,
            bool inline = true,
            bool force = true) {

            m_openBraceOnNewLine = openBraceOnNewLine;
            m_inline = inline;
            m_force = force;
        }

        protected override IEnumerable<object> Content() {
            if (m_force)
                return TextNode.ArbitraryContent;

            return Children();
        }
        protected override IEnumerable<object> Children(TextNodeContext context) {
            var containsContent = context.ContainsContent(Children());
            var content = new TextSingleSpaced() { Children() };
            var inline = m_inline && !context.ContainsNewLine(content);

            if (inline) {
                yield return CsPunctuation.OpenBrace;
                yield return " ";

                if (containsContent) {
                    yield return Children();
                    yield return " ";
                }

                yield return CsPunctuation.CloseBrace;

            } else {
                if (m_openBraceOnNewLine)
                    yield return Environment.NewLine;

                yield return CsPunctuation.OpenBrace;
                yield return Environment.NewLine;

                if (containsContent) {
                    yield return new TextIndent() { 
                        content 
                    };
                    yield return Environment.NewLine;
                }

                yield return CsPunctuation.CloseBrace;
            }
        }
    }

    [TextNodeContainsContent]
    internal sealed class CsIdentifier : CsNode {

        private object m_identifier;

        internal CsIdentifier(object identifier) {
            m_identifier = identifier;
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            yield return m_identifier;
        }
        internal override SyntaxNodeOrToken Activate(string text) {
            return Syntax.Identifier(text);            
        }
    }

    [TextNodeContainsContent]
    internal sealed class CsName : CsNode {

        private object m_name;

        public CsName(object name) {
            m_name = name;
        }

        internal override IEnumerable<object> Nodes(TextNodeContext context) {
            yield return m_name;
        }
        internal override SyntaxNodeOrToken Activate(string text) {
            return Syntax.ParseName(text);
        }
    }
}
